package engine.scene;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;

public class Frustum {

	private FloatBuffer projectionMatrix;
	private FloatBuffer modelviewMatrix;
	private IntBuffer viewPort;

	private float[][] frustum;
	private float[] result;
	
	public Frustum() {
		projectionMatrix = BufferUtils.createFloatBuffer(16);
		modelviewMatrix = BufferUtils.createFloatBuffer(16);
		viewPort = BufferUtils.createIntBuffer(16);

		frustum = new float[6][4];
		result = new float[16];
	}
	
	public void update() {
		updateResult();

		// Jede Flaeche extrahieren und normalisieren
		// rechts
		frustum[0][0] = result[3] - result[0];
		frustum[0][1] = result[7] - result[4];
		frustum[0][2] = result[11] - result[8];
		frustum[0][3] = result[15] - result[12];

		// links
		frustum[1][0] = result[3] + result[0];
		frustum[1][1] = result[7] + result[4];
		frustum[1][2] = result[11] + result[8];
		frustum[1][3] = result[15] + result[12];

		// unten
		frustum[2][0] = result[3] + result[1];
		frustum[2][1] = result[7] + result[5];
		frustum[2][2] = result[11] + result[9];
		frustum[2][3] = result[15] + result[13];

		// oben
		frustum[3][0] = result[3] - result[1];
		frustum[3][1] = result[7] - result[5];
		frustum[3][2] = result[11] - result[9];
		frustum[3][3] = result[15] - result[13];

		// hinten
		frustum[4][0] = result[3] - result[2];
		frustum[4][1] = result[7] - result[6];
		frustum[4][2] = result[11] - result[10];
		frustum[4][3] = result[15] - result[14];

		// vorne
		frustum[5][0] = result[3] + result[2];
		frustum[5][1] = result[7] + result[6];
		frustum[5][2] = result[11] + result[10];
		frustum[5][3] = result[15] + result[14];

		normalizeFrustum();
	}
	
	private void updateResult() {
		projectionMatrix.rewind();
		GL11.glGetFloat(GL11.GL_PROJECTION_MATRIX, projectionMatrix);

		modelviewMatrix.rewind();
		GL11.glGetFloat(GL11.GL_MODELVIEW_MATRIX, modelviewMatrix);
		
		viewPort.rewind();
		GL11.glGetInteger(GL11.GL_VIEWPORT, viewPort);

		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				float result = 0;
				for (int k = 0; k < 4; k++) {
					result += modelviewMatrix.get(i * 4 + k)
							* projectionMatrix.get(k * 4 + j);
				}

				this.result[i * 4 + j] = result;
			}
		}
	}

	private void normalizeFrustum() {
		for (int i = 0; i < frustum.length; i++) {
			float length = (float) Math.sqrt(frustum[i][0] * frustum[i][0]
					+ frustum[i][1] * frustum[i][1] + frustum[i][2]
					* frustum[i][2]);

			frustum[i][0] /= length;
			frustum[i][1] /= length;
			frustum[i][2] /= length;
			frustum[i][3] /= length;
		}
	}
	
	public boolean sphereInFrustum(double x, double y, double z, double radius) {
		for(int i = 0; i < frustum.length; i++) {
			if((frustum[i][0] * x + frustum[i][1] * y + frustum[i][2] * z + frustum[i][3]) <= -radius) {
				return false;
			}
		}
		return true;
	}
	
	public boolean cubeInFrustum(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) {
	   int p;

	   for( p = 0; p < 6; p++ ) {
	      if( frustum[p][0] * minX + frustum[p][1] * minY + frustum[p][2] * minZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * maxX + frustum[p][1] * minY + frustum[p][2] * minZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * minX + frustum[p][1] * maxY + frustum[p][2] * minZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * maxX + frustum[p][1] * maxY + frustum[p][2] * minZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * minX + frustum[p][1] * minY + frustum[p][2] * maxZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * maxX + frustum[p][1] * minY + frustum[p][2] * maxZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * minX + frustum[p][1] * maxY + frustum[p][2] * maxZ + frustum[p][3] > 0 )
	         continue;
	      if( frustum[p][0] * maxX + frustum[p][1] * maxY + frustum[p][2] * maxZ + frustum[p][3] > 0 )
	         continue;
	      return false;
	   }
	   return true;
	}
	
	public float[] getResult() {
		return result;
	}
	
	public FloatBuffer getModelviewMatrix() {
		modelviewMatrix.rewind();
		return modelviewMatrix;
	}
	
	public FloatBuffer getProjectionMatrix() {
		projectionMatrix.rewind();
		return projectionMatrix;
	}
	
	public IntBuffer getViewPort() {
		viewPort.rewind();
		return viewPort;
	}
}
